package org.juiceshop.musi;



import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.juiceshop.musi.midi.Instrument;

public class NoteFactory {

    private static enum NotePitch {
    	C(0),D(2),E(4),F(5),G(7),A(9),B(11),R(Note.REST);
    	
    	private int _pitch;
    	private NotePitch(int pitch) {
    		_pitch = pitch;
    	}
    	public int getPitch() { return _pitch; }
    }
	
    private final static Map<String, Instrument> instrumentNames;

	static {
        instrumentNames = new HashMap<String, Instrument>();
        for (Instrument instr : Instrument.values()) {
            instrumentNames.put(instr.toString(),instr);
        }
    }
	
    static final int OCTAVE = 12;
    
       
	protected Note createNote(
			final int pitch,
			final int accidentials,
			final double value,
			final int dynamic,
			final double articulation,
			final boolean slur) {
		return new Note(this,pitch,accidentials,value,dynamic,articulation,slur);
	}

protected Note copyNote(Note n) {
return new Note(this,n);
}
    
    
    
	public Note note(Map<String,Object> values) {
		Note note = (Note)_current.copy(values);
		return note;
    }
    
    public Note note(Map<String,Object> values, int pitch) {
		Note note = (Note)_current.copy(values);
		note.setPitch(pitch);
		return note;
    }
    
    public Note note(int pitch) {
        return note(NOVALUES,pitch);
    }
    
    public Note note (Map<String,Object> values, String letter, Integer octave) { 
        int pitch = NotePitch.valueOf(letter).getPitch();
        if (pitch!=Note.REST) {
            pitch += octave*OCTAVE + _current.getPitch();
        }
        Note note = note(values,pitch);
        int adjustment = _current.getKey().adjust(pitch) - pitch;
        return adjustment==0 ? note : adjustment<0 ? note.getFlat() : note.getSharp();
    }
    
    public Note note (String letter, Integer octave) {
        return note(NOVALUES,letter,octave);
    }
    
    public Note note (String letter) {
    	return note(NOVALUES,letter,0);
    }

    public Note rest() {
        return note(NOVALUES,Note.REST);
    }
    
    public Note rest(Map<String,Object> values) {
    	return note(values,Note.REST);
    }

    public Phrase phrase (Map<String,Object> values, MusicObject...elems) {
        return new Phrase(values,elems);
        
    }
    
    public Phrase phrase (MusicObject...elems) {
        return new Phrase(elems);
    }
    
    public Phrase phrase (List<MusicObject> list) {
    	MusicObject[] array = list.toArray(new MusicObject[list.size()]);
    	return phrase(array);
    }
    
    public Chord chord (Note note, MusicObject...more) {
        return new Chord(note,more);
    }
    
    public Chord chord (Note note, int...relPitches) {
        return new Chord(note,relPitches);
    }
        
    /**
     * Execute closure with modified default settings.
     * @param props Map with default values that shall be valid for
     * closure execution.
     */
	void with (Map<String,Object> props, Closure closure) {
		Note oldCurrent = _current;
        _current = (Note)oldCurrent.copy(props);
        Closure closureCopy = (Closure)closure.clone();
		closureCopy.setDelegate(this);
		MusicObjectCategory.use(closureCopy);
        _current = oldCurrent;
	}
	
	void with (Closure closure) {
		Map<String,Object> props = Collections.emptyMap();
		with(props,closure);
	}
	
	
	/**
	 * Invokes additional methods.
	 * @see #tryCall(String,Object)
	 * @see GroovyObject#methodMissing()
	 */
	public Object methodMissing(String name, Object args) {
		Object result = tryCall(name,args);
		if (result!=null) return result;
		throw new MissingMethodException(name,NoteFactory.class,(Object[])args);		
	}

	/**
	 * Retrieves additional properties.
	 * @see #tryGet(String)
	 * @see GroovyObject@propertyMissing()
	 */
	public Object propertyMissing(String name) {
		Object result = tryGet(name);
		if (result!=null) return result;
		throw new MissingPropertyException(name,NoteFactory.class);		
	}

    Object tryCall (String method, Object args) {
        return tryNote(method,args);
    }
    
    Object tryGet (String property) {
        Object result = tryNote(property,null);
        if (result!=null) return result;
        return Instrument.fromString(property);
    }
	    

	/**
	 * Try to construct a Note from a method or property call.
	 * Call must have one of the following forms (where <code><i>C</i></code> is
	 * one of <code>A...G</code> or <code>R</code>:
	 * <ul>
	 * <li><code><i>C</i>()</code> or <code><i>C</i></code></li>
	 * <li><code><i>C</i>(<i>octave</i>)</code>
	 * <li><code><i>C</i>(<i>arg</i>:<i>value</i>,...)</code>
	 * <li><code><i>C</i>(<i>octave</i>,<i>arg</i>:<i>value</i>,...)</code>
	 * </ul>
	 * @param name Method or property name.
	 * @param args Method arguments (Array of Object)
	 * @return {@link Note} instance or <code>null</code>.
	 */
	private Note tryNote(String name, Object args) {
		if (!name.matches("[A-GR]")) return null;
		Object[] argArray = (Object[])args;
		int octave = 0;
		Map<String,Object> argMap = NOVALUES;
		if (argArray==null || argArray.length==0) {
			// OK
		} else if (argArray.length==1 && argArray[0] instanceof Integer) {
			octave = (Integer)argArray[0];
		} else if (argArray.length==1 && argArray[0] instanceof Map<?,?>) {
			argMap = toMap(argArray[0]);
		} else if (argArray.length==2 && argArray[0] instanceof Map<?,?> &&
				argArray[1] instanceof Integer) {
			argMap = toMap(argArray[0]);
			octave = (Integer)argArray[1];
		} else {
			return null;
		}
		return note(argMap,name,octave);
	}

	
	@SuppressWarnings("unchecked")
	private Map<String,Object> toMap(Object obj) {
		return (Map<String,Object>)obj;	
	}
	
}
